﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml;
using System.Xml.Serialization;
using KBEngine.Setting;

/*
 √  1.层级合并  auth/username  auth/password
 √  2.写入时 List的差异比较
 √  3.List中复杂结构不能合并层级
 √  4.List根结构写入
 */

namespace KBEngine.Setting
{
    [Serializable]
    public class XmlSerializeOpt
    {
        private bool isReset = false; //强制匹配模式(是否需要xmlNode与Class进行匹配<以class为基准>)

        private static XmlReaderSettings ReaderSettings
        {
            get
            {
                XmlReaderSettings settings = new XmlReaderSettings();
                settings.IgnoreComments = true;
                return settings;
            }
        }
        private static XmlWriterSettings WriterSettings
        {
            get
            {
                XmlWriterSettings settings = new XmlWriterSettings();
                settings.Indent = true;
                settings.Encoding = System.Text.Encoding.UTF8;
                settings.IndentChars = "\t";
                settings.NewLineChars = "\n";
                settings.OmitXmlDeclaration = true;
                return settings;
            }
        }


        //------------------------ Xml -------------
        /// <summary>
        /// 反序列化Xml文件为类
        /// </summary>
        /// <typeparam name="T">类型</typeparam>
        /// <param name="path">文件</param>
        /// <returns></returns>
        public static T XmlDeserializeByFile<T>(string path)
        {
            T t = default;
            try
            {
                using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
                {
                    XmlSerializer xs = new XmlSerializer(typeof(T));
                    using (XmlReader xmlReader = XmlReader.Create(fs, ReaderSettings))
                    { 
                        t = (T)xs.Deserialize(xmlReader);
                    }
                }
            }
            catch (Exception e)
            {
                Logger.LogError("此xml文件无法转成类: " + path + "," + e);
            }
            return t;
        }
        /// <summary>
        /// 反序列化Xml文件为类(文本数据)
        /// </summary>
        /// <typeparam name="T">类型</param>
        /// <param name="txt">文本</param>
        /// <returns></returns>
        public static T XmlDeserializeByTxt<T>(string txt)
        {
            T t = default(T);
            try
            {
                using (TextReader tr = new StringReader(txt))
                {
                    XmlSerializer xs = new XmlSerializer(typeof(T));
                    using (XmlReader xmlReader = XmlReader.Create(tr, ReaderSettings))
                    {
                        t = (T)xs.Deserialize(xmlReader);
                    }
                }
            }
            catch (Exception e)
            {
                Logger.LogError("此xml文本无法转成二进制: " + txt.ToString() + "," + e);
            }
            return t;
        }
        /// <summary>
        /// 反序列化Xml文件为类(内存数据)
        /// </summary>
        /// <typeparam name="T">类型</param>
        /// <param name="bytes">字节数组</param>
        /// <returns></returns>
        public static T XmlDeserialize<T>(byte[] bytes)
        {
            T t = default(T);
            try
            {
                using (Stream stream = new MemoryStream(bytes))
                {
                    XmlSerializer xs = new XmlSerializer(typeof(T));
                    using (XmlReader xmlReader = XmlReader.Create(stream, ReaderSettings))
                    {
                        t = (T)xs.Deserialize(xmlReader);
                    }
                }
            }
            catch (Exception e)
            {
                Logger.LogError("此xml字节数组无法转成二进制: " + bytes.ToString() + "," + e);
            }
            return t;
        }
        /// <summary>
        /// 类序列化成xml文件
        /// </summary>
        /// <param name="path">文件</param>
        /// <param name="obj">类型</param>
        /// <returns>成功,返回true</returns>
        public static bool XmlSerializeToFile(string path, System.Object obj)
        {
            try
            {
                using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite))
                {
                    using (StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.UTF8))
                    {
                        XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
                        namespaces.Add(string.Empty, string.Empty);
                        XmlSerializer xs = new XmlSerializer(obj.GetType());
                        using (XmlWriter xmlWriter = XmlWriter.Create(sw, WriterSettings))
                        {
                            xs.Serialize(xmlWriter, obj, namespaces);
                        }
                    }
                }
                return true;
            }
            catch (Exception e)
            {
                Logger.LogError("此类无法转换成xml " + obj.GetType() + "," + e);
            }
            return false;
        }
        /// <summary>
        /// 类序列化成xml文本
        /// </summary>
        /// <param name="path">文件</param>
        /// <param name="obj">类型</param>
        /// <returns>成功,返回true</returns>
        public static bool XmlSerializeToTxt(ref string txt, System.Object obj)
        {
            try
            {
                XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
                namespaces.Add(string.Empty, string.Empty);
                var sb = new StringWriter();
                XmlSerializer xs = new XmlSerializer(obj.GetType());
                using (XmlWriter xmlWriter = XmlWriter.Create(sb, WriterSettings))
                {
                    xs.Serialize(xmlWriter, obj, namespaces);
                }
                txt = sb.ToString();
                return true;
            }
            catch (Exception e)
            {
                Logger.LogError("此类无法转换成xml " + obj.GetType() + "," + e);
            }
            return false;
        }


        public static KBEngineDefaults ReadKBESetting(string defaultFile, string projectFile = null)
        {
            var kBEngineDefaults = XmlDeserializeByFile<KBEngineDefaults>(defaultFile);
            if (projectFile != null)
            {
                XmlSerializeOpt _xmlSerialize = new XmlSerializeOpt();
                _xmlSerialize.isReset = false;
                XmlDocument xmlDoc = new XmlDocument();
                using (XmlReader reader = XmlReader.Create(projectFile, ReaderSettings))
                {
                    xmlDoc.Load(reader);
                    var root = xmlDoc.SelectSingleNode("root");
                    _xmlSerialize.Deserialize(kBEngineDefaults, root);
                }
            }
            return kBEngineDefaults;
        }
        public static bool WriteKBESetting(string projectFile, KBEngineDefaults obj, string compDefaultFile = null)
        {
            KBEngineDefaults kBEngineDefaults = compDefaultFile != null ? XmlDeserializeByFile<KBEngineDefaults>(compDefaultFile) : null;
            XmlDocument xmlDoc = new XmlDocument();
            XmlNode root = xmlDoc.CreateElement("root");
            xmlDoc.AppendChild(root);
            XmlSerializeOpt _xmlSerialize = new XmlSerializeOpt();
            _xmlSerialize.Serialize(obj, root, kBEngineDefaults);
            using (XmlWriter xmlWriter = XmlWriter.Create(projectFile, WriterSettings))
            {
                xmlDoc.Save(xmlWriter);
            }
            return true;
        }


        #region LoadSetting
        internal void Deserialize(object obj, XmlNode xmlNode)
        {
            if (xmlNode == null && !isReset) return;
            var fieldArray = obj.GetType().GetFields();
            var agrs = new Agrs();
            foreach (var fieldInfo in fieldArray)
            {
                string elementName = string.Empty;
                bool isListDetch = true;
                var attrArray = fieldInfo.GetCustomAttributes(false);
                foreach (var attrObj in attrArray)
                {
                    var type = attrObj.GetType();
                    if (type == typeof(XmlElementAttribute))
                    {
                        if (string.IsNullOrEmpty(((XmlElementAttribute)attrObj).ElementName))
                            isListDetch = false;
                        else
                            elementName = ((XmlElementAttribute)attrObj).ElementName;
                    }
                }
                string fieldName = string.IsNullOrEmpty(elementName) ? fieldInfo.Name : elementName;
                var node = isListDetch ? xmlNode.SelectSingleNode(fieldName) : xmlNode;
                if (node == null && (!isReset)) continue;
                try
                {
                    agrs.obj = fieldInfo.GetValue(obj);
                    agrs.type = fieldInfo.FieldType;
                    agrs.xmlNode = node;
                    fieldInfo.SetValue(obj, ParserValue(agrs));
                }
                catch (Exception e)
                {
                    throw new Exception(fieldInfo.Name + "::" + e.Message);
                }
            }
        }
        private object ParserValue(Agrs args)
        {
            if (args.xmlNode == null) throw new NullReferenceException($"未序列化属性{args.type.Name} xmlNode为空");
            if (args.type == typeof(string)) return ParseStr(args);
            if (args.type == typeof(bool)) return ParseBool(args);
            if (args.type == typeof(byte)) return byte.Parse(args.xmlNode.InnerText);
            if (args.type == typeof(sbyte)) return sbyte.Parse(args.xmlNode.InnerText);
            if (args.type == typeof(ushort)) return ushort.Parse(args.xmlNode.InnerText);
            if (args.type == typeof(short)) return short.Parse(args.xmlNode.InnerText);
            if (args.type == typeof(int)) return int.Parse(args.xmlNode.InnerText);
            if (args.type == typeof(uint)) return uint.Parse(args.xmlNode.InnerText);
            if (args.type == typeof(ulong)) return ulong.Parse(args.xmlNode.InnerText);
            if (args.type == typeof(long)) return long.Parse(args.xmlNode.InnerText);
            if (args.type == typeof(float)) return float.Parse(args.xmlNode.InnerText);
            if (args.type == typeof(double)) return double.Parse(args.xmlNode.InnerText);
            if (args.type.IsGenericType && args.type.IsClass) return ParseList(args);
            if (args.type.IsClass) return ParseObj(args);
            throw new NotSupportedException($"未序列化属性{args.type.Name}");
        }
        private object ParseStr(Agrs args)
        {
             return args.xmlNode.InnerText.Trim();
        }
        private object ParseObj(Agrs args)
        {
            if (args.obj == null)
                args.obj = Activator.CreateInstance(args.type);
            Deserialize(args.obj, args.xmlNode);
            return args.obj;
        }
        private object ParseList(Agrs args)
        {
            if (args.obj == null)
                args.obj = Activator.CreateInstance(args.type);
            object[] invokeArgs = new object[1];
            var methodInfo = args.type.GetMethod("Add");
            var newArgs = new Agrs();
            foreach (XmlNode subNode in args.xmlNode)
            {
                if (subNode.Name.Trim() != args.itemName)
                    continue;
                newArgs.obj = null;
                newArgs.type = args.type.GenericTypeArguments[0];
                newArgs.xmlNode = subNode;
                invokeArgs[0] = ParserValue(newArgs);
                methodInfo.Invoke(args.obj, invokeArgs);
            }
            return args.obj;
        }
        private object ParseBool(Agrs args)
        {
             return bool.Parse(args.xmlNode.InnerText);
        }
        #endregion

        #region SaveSetting
        internal bool Serialize(object obj, XmlNode xmlNode, object diffObj)
        {
            bool isAdd = false;
            //if (xmlNode == null && !isReset) return;
            var fieldArray = obj.GetType().GetFields();
            var agrs = new Agrs();
            foreach (var fieldInfo in fieldArray)
            {
                string elementName = string.Empty;
                bool isListDetch = true;
                var attrArray = fieldInfo.GetCustomAttributes(false);
                foreach (var attrObj in attrArray)
                {
                    var type = attrObj.GetType();
                    if (type == typeof(XmlElementAttribute))
                    {
                        if(string.IsNullOrEmpty(((XmlElementAttribute)attrObj).ElementName))
                            isListDetch = false;
                        else
                            elementName = ((XmlElementAttribute)attrObj).ElementName;
                    }
                }
                string fieldName = string.IsNullOrEmpty(elementName) ? fieldInfo.Name : elementName;
                var headNode = CreateDethNode(xmlNode, fieldName, out XmlNode node);
                if (!isListDetch)
                {
                    node = xmlNode.OwnerDocument.CreateElement("listRoot");
                    headNode = node;
                }
                //if (node == null && isNull) continue;
                try
                {
                    agrs.obj = fieldInfo.GetValue(obj);
                    agrs.type = fieldInfo.FieldType;
                    agrs.xmlNode = node;
                    agrs.diffClassObj = diffObj == null ? null : fieldInfo.GetValue(diffObj);
                    var isAddNew = SerializeValue(agrs);
                    isAdd |= isAddNew;
                    if (!isListDetch)
                    {
                        while(node.HasChildNodes)
                            xmlNode.AppendChild(node.FirstChild);
                        continue;
                    }
                    if (isAddNew)
                        MergeDethNode(xmlNode, fieldName, headNode); //层级合并
                }
                catch (Exception e)
                {
                    throw new Exception(fieldInfo.Name + "::" + e.Message);
                }
            }
            return isAdd;
        }
        private bool SerializeValue(Agrs args)
        {
            if (args.xmlNode == null) throw new NullReferenceException($"未序列化属性{args.type.Name} xmlNode为空");
            if (args.type == typeof(string)) return SerializeStr(args);
            if (args.type == typeof(bool)) return SerializeBool(args);
            if (args.type == typeof(byte)) return SerializeStr(args);
            if (args.type == typeof(sbyte)) return SerializeStr(args);
            if (args.type == typeof(ushort)) return SerializeStr(args);
            if (args.type == typeof(short)) return SerializeStr(args);
            if (args.type == typeof(int)) return SerializeStr(args);
            if (args.type == typeof(uint)) return SerializeStr(args);
            if (args.type == typeof(ulong)) return SerializeStr(args);
            if (args.type == typeof(long)) return SerializeStr(args);
            if (args.type == typeof(float)) return SerializeFloat(args);
            if (args.type == typeof(double)) return SerializeFloat(args);
            if (args.type.IsGenericType && args.type.IsClass) return SerializeList(args);
            if (args.type.IsClass) return SerializeObj(args);
            throw new NotSupportedException($"未序列化属性{args.type.Name}");
        }
        private bool SerializeStr(Agrs args)
        {
            args.xmlNode.InnerText = args.obj.ToString();
            if (args.diffClassObj != null && args.xmlNode.InnerText == args.diffClassObj.ToString())
                return false;

            if (string.IsNullOrEmpty(args.xmlNode.InnerText))
                args.xmlNode.InnerText = "  ";
            return true;
        }
        private bool SerializeFloat(Agrs args)
        {
            if (args.diffClassObj != null && args.obj.ToString() == args.diffClassObj.ToString())
                return false;

            args.xmlNode.InnerText = string.Format("{0:0.0###########}", args.obj);
            //if (string.IsNullOrEmpty(xmlNode.InnerText))
            //    xmlNode.InnerText = "  ";
            return true;
        }
        private bool SerializeObj(Agrs args)
        {
            if (args.obj == null) throw new ArgumentNullException();
            return Serialize(args.obj, args.xmlNode, args.diffClassObj);
        }
        private bool SerializeList(Agrs args)
        {
            if (args.obj == null) throw new ArgumentNullException();
            bool isAdd = false;
            object[] invokeArgs = new object[1];
            var countProperty = args.type.GetProperty("Count");
            var itemProperty = args.type.GetProperty("Item");
            int count = Convert.ToInt32(countProperty.GetValue(args.obj, null));
            int diffCount = 0;
            if (args.diffClassObj != null)
            {
                //比较List中的元素是否相同;相同则直接返回
                diffCount = Convert.ToInt32(countProperty.GetValue(args.diffClassObj, null));
                if (count == diffCount)
                {
                    bool isChange = false;
                    for (int i = 0; i < count && !isChange; i++)
                    {
                        invokeArgs[0] = i;
                        if (itemProperty.GetValue(args.obj, invokeArgs).ToString() != itemProperty.GetValue(args.diffClassObj, invokeArgs).ToString())
                            isChange = true;
                    }
                    if (!isChange)
                        return false;
                }
            }
            //挨个序列化
            var newArgs = new Agrs();
            newArgs.type = args.type.GenericTypeArguments[0];
            var defaultObj = (newArgs.type != typeof(string) && !newArgs.type.IsGenericType && newArgs.type.IsClass) ? Activator.CreateInstance(newArgs.type) : null;
            for (int i = 0; i < count; i++)
            {
                invokeArgs[0] = i;
                newArgs.obj = itemProperty.GetValue(args.obj, invokeArgs);
                string nodeName = args.itemName;
                newArgs.xmlNode = args.xmlNode.AppendChild(args.xmlNode.OwnerDocument.CreateElement(nodeName));
                newArgs.diffClassObj = diffCount > i ? itemProperty.GetValue(args.diffClassObj, invokeArgs) : defaultObj;
                isAdd |= SerializeValue(newArgs);
            }
            //无内容,显示根节点
            if (count == 0)
                return true;
            return isAdd;
        }
        private bool SerializeBool(Agrs args)
        {
            bool b = (bool)args.obj;
            if (args.diffClassObj != null && b == (bool)args.diffClassObj)
                return false;

            args.xmlNode.InnerText = b.ToString().ToLower();
            return true;
        }

        private XmlNode CreateDethNode(XmlNode xmlNode, string pathName, out XmlNode lastNode)
        {
            XmlNode headNode = null;
            XmlNode tempNode = null;
            XmlDocument xmlDoc = xmlNode.OwnerDocument;
            var parArray = pathName.Split('/');
            foreach (var item in parArray)
            {
                XmlNode newNode = xmlDoc.CreateElement(item);
                if (tempNode != null)
                    tempNode.AppendChild(newNode);
                tempNode = newNode;
                if (headNode == null)
                    headNode = tempNode;
            }
            lastNode = tempNode;
            return headNode;
        }
        private void MergeDethNode(XmlNode xmlNode, string pathName, XmlNode headNode)
        {
            var parArray = pathName.Split('/');
            var tempNode = xmlNode;
            foreach (var item in parArray)
            {
                var selNode = tempNode.SelectSingleNode(item);
                if (selNode != null)
                {
                    tempNode = selNode;
                    headNode = headNode.FirstChild;
                }
                else
                {
                    tempNode.AppendChild(headNode);
                    break;
                }
            }
        }
        #endregion

        private class Agrs
        {
            public object obj;
            public System.Type type;
            public XmlNode xmlNode;
            public string itemName = "item";

            public object diffClassObj;//仅Save中对比使用
        }
    }
}
